
%----------------------------------------------------------------------------------------
%	PACKAGES AND OTHER DOCUMENT CONFIGURATIONS
%----------------------------------------------------------------------------------------

\documentclass[fleqn,10pt]{SelfArx} % Document font size and equations flushed left

%--------------------------- Fonts ----------------------------
\setmonofont{Inconsolata}

%--------------------------- COLUMNS --------------------------
\setlength{\columnsep}{0.55cm} % Distance between the two columns of text
\setlength{\fboxrule}{0.75pt} % Width of the border around the abstract

%----------------------------------------------------------------------------------------
%	ARTICLE INFORMATION
%----------------------------------------------------------------------------------------

\JournalInfo{2020} % Journal information
\Archive{lxk} % Additional notes (e.g. copyright, DOI, review/research article)

\PaperTitle{Modbus tutorial} % Article title

\Authors{Gaal} % Authors
\affiliation{DHU} % Author affiliation
\affiliation{lxiangkun@163.com} % Author affiliation

\Keywords{Modbus} % Keywords - if you don't want any simply remove all the text between the curly brackets
\newcommand{\keywordname}{关键词} % Defines the keywords heading name

\makeglossaries
\newglossaryentry{adu}{name=ADU, description={Application Data Unit}}
\newglossaryentry{mb}{name=MB, description={MODBUS Protocol}}
\newglossaryentry{mbap}{name=MBAP, description={MODBUS Application Protocol}}
\newglossaryentry{pdu}{name=PDU, description={Protocol Data Unit}}
\newglossaryentry{plc}{name=PLC, description={Programmable Logic Controller}}
\newglossaryentry{tcp}{name=TCP, description={Transmission Control Protocol}}
\newglossaryentry{lrc}{name=LRC, description={Longitudinal Redundancy Check}}
\newglossaryentry{crc}{name=CRC, description={Cyclic Redundancy Check}}
\newglossaryentry{hdlc}{name=HDLC, description={High level Data Link Control}}
%----------------------------------------------------------------------------------------
%	ABSTRACT
%----------------------------------------------------------------------------------------

\Abstract{MODBUS总线协议的相关介绍。}

%----------------------------------------------------------------------------------------

\begin{document}

\flushbottom % Makes all text pages the same height

\maketitle % Print the title and abstract box

\tableofcontents

\thispagestyle{empty} % Removes page numbering from the first page

%----------------------------------------------------------------------------------------
%	ARTICLE CONTENTS
%----------------------------------------------------------------------------------------

\section*{简介} % The \section*{} command stops section numbering

\addcontentsline{toc}{section}{简介} % Adds this section to the table of contents

ModBus是1979年\gls{plc}厂商Modicon公司 -- 现为施耐德旗下品牌 -- 为其PLC通讯而开发的一种通讯协议。
Modbus问世后，在很短时间内成为了工业通讯领域的事实标准。最初的ModBus物理层为RS-232，
后来大多数使用RS-485。

在工业环境中使用 Modbus 的主要原因是：
\begin{itemize}
    \item 在考虑工业应用的情况下开发
    \item 公开发布，免版税
    \item 易于部署和维护
    \item 允许供应商无限制移动原始位或字
\end{itemize}

Modbus 组织\cite{modbusorganizationModbusOrganization2020}是 Modbus 兼容设备的用户和供应商协会。
Modbus 协议的开发和更新已由 Modbus 组织管理。

%------------------------------------------------
\section{主要协议版本}
\begin{description}
    \item [Modbus RTU] - 用于串行通信，并使用紧凑的二进制数据表示协议通信。
    RTU 格式遵循命令/数据，循环冗余校验和校验和作为错误检查机制，以确保数据的可靠性。
    Modbus RTU 是 Modbus 最常用的实现方式。必须连续传输 Modbus RTU 消息，
    而不会出现字符间的犹豫。 Modbus 消息由空闲（静默）时段构成（分离）。
    \item [Modbus ASCII] - 用于串行通信，并使用 ASCII 字符进行协议通信。
    ASCII 格式使用纵向冗余校验校验和。 Modbus ASCII 消息由前导冒号（“：”）
    和尾随换行符（CR/LF）构成。
    \item [Modbus TCP/IP或Modbus TCP] - 这是一种 Modbus 变体，
    用于通过 TCP/IP 网络进行通信，通过端口 502 连接。它不需要校验和计算，
    因为较低层已经提供校验和保护。
    \item [Modbus over TCP/IP(或TCP)或Modbus RTU/IP] - 这是一种
    Modbus 变体，与 Modbus TCP 的不同之处在于，与 Modbus RTU 一样，
    有效载荷中包含校验和。
    \item [Modbus over UDP] - 一些人尝试在IP网络上使用 Modbus over UDP，
    这消除了 TCP 所需的开销。
    \item [Modbus Plus（Modbus+， MB+或MBP）] - Modbus Plus 是施耐德
    电气专有的，与其他变体不同，它支持多个主站之间的点对点通信。它需要一个专用
    的协处理器来处理类似 \gls{hdlc} 的快速令牌轮换。它使用 1 Mbit/s 的双绞线，
    并在每个节点处包含变压器隔离，这使其转换/边沿触发而不是电压/电平触发。
    将 Modbus Plus 连接到计算机（通常是用于 ISA ， PCI 或 PCMCIA 总线的
    卡）需要特殊硬件。
    \item [Pemex Modbus] - 这是标准 Modbus 的扩展，支持历史和流量数据。
    它专为 Pemex 石油和天然气公司设计，用于过程控制，从未得到广泛采用。
    \item [Enron Modbus] - 这是 Enron 公司开发的标准 Modbus 的另一个
    扩展，支持 32 位整数和浮点变量以及历史和流量数据。使用标准地址映射数据类型。
    历史数据符合美国石油协会（API）行业标准，用于存储数据。[需要引证]
\end{description}

对于前4种协议变量，数据模型和函数调用是相同的; 只有封装是不同的。 
但是，变体不可互操作，帧格式也不可互操作\cite{wikipediaModbusWikipedia2020}。

\section{通讯和设备}
准备使用 Modbus 进行通信的每个设备都有一个唯一的地址。在串行和 MB+ 网络中，
只有指定为主节点的节点才能发起命令。在以太网上，任何设备都可以发送 Modbus 命令，
尽管通常只有一个主设备会这样做。 Modbus 命令包含设备的 Modbus 地址（1到247）。
只有预期的设备才会对该命令起作用，即使其他设备可能会收到它（例外情况是发送到节点 0 
的特定可广播命令，它们被作用但未被确认）。所有 Modbus 命令都包含校验和信息，
以允许接收方检测传输错误。基本 Modbus 命令可以指示 RTU 更改其中一个寄存器中的值，
控制或读取 I / O 端口，并命令设备发回其寄存器中包含的一个或多个值。

有许多调制解调器和网关支持 Modbus，因为它是一个非常简单且经常被复制的协议。
其中一些是专门为此协议设计的。不同的实现使用有线，无线通信，例如在 ISM 频带中，
甚至是短消息服务（SMS）或通用分组无线服务（GPRS）。无线网络的一种常见
设计利用网状网络。设计人员必须克服的典型问题包括高延迟和时序问题。

\section{数据模型}
Modbus 支持连接到同一网络的许多设备之间的通信，例如，测量温度和湿度的系统，
并将结果传送给计算机。 Modbus 通常用于将监控计算机与远程终端单元（RTU）
连接在监控和数据采集（SCADA）系统中。

许多数据类型都是从其在驱动继电器中的使用命名的：
单比特物理输出称为线圈，单比特物理输入称为离散输入或接触。

以下是 Modbus 从站设备向 Modbus 主站设备提供的对象类型表。
\begin{table}[htbp]
    \centering
    \caption{MODBUS基础对象类型}
    \label{tab:primary-table}
    \begin{tabular}{|c|l|l|}
    \hline
    对象类型  & 访问权限 & 大小      \\ \hline
    线圈    & 读写   & 1 bit   \\ \hline
    离散输入  & 只读   & 1 bit   \\ \hline
    输入寄存器 & 只读   & 16 bits \\ \hline
    保持寄存器 & 读写   & 16 bits \\ \hline
    \end{tabular}
\end{table}

\section{报文格式}
Modbus帧由应用数据单元（\gls{adu}）组成，它包含协议数据单元（\gls{pdu}）：
\begin{itemize}[noitemsep]
    \item ADU = 地址 + PDU + 错误校验
    \item PDU = 功能代码 + 数据
\end{itemize}

\begin{figure}[ht]
    \includegraphics[scale=0.75]{general modbus frame.pdf}
    \caption{通用MODBUS帧\cite{modbusorganizationMODBUSAPPLICATIONPROTOCOL2012}}
    \label{fig:GeneralMODBUSFrame}
\end{figure}

Modbus 数据帧中值的字节顺序是 big-endian（MSB，首先接收的值的最高有效字节）。

\subsection*{Modbus RTU 帧格式（主要用于 EIA-485 等 8 位异步线路）}   
使用RTU模式，消息发送至少要以3.5个字符时间的停顿间隔开始。
在最后一个传输字符之后，一个至少3.5个字符时间的停顿标定了消息的结束。
一个新的消息可在此停顿后开始。

整个消息帧必须作为一连续的流转输。如果在帧完成之前有超过1.5个字符时间的停顿时间，
接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。同样地，
如果一个新消息在小于3.5个字符时间内接着前个消息开始，接收的设备将认为
它是前一消息的延续。这将导致一个错误，因为在最后的CRC域的值不可能是正确的。
% Please add the following required packages to your document preamble:
% \usepackage[table,xcdraw]{xcolor}
% If you use beamer only pass "xcolor=table" option, i.e. \documentclass[xcolor=table]{beamer}
\begin{table}[ht]
    \centering
    \caption{MODBUS RTU帧格式}
    \label{tab:frame-formats-rtu}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    名称  & 长度(bits)   & 功能                                                              \\ \hline
    起始  & 28         & \begin{tabular}[c]{@{}l@{}}至少3.5 个字符的空闲时间\\ （标记条件）\end{tabular} \\ \hline
    地址  & 8          & 站地址                                                             \\ \hline
    功能  & 8          & \begin{tabular}[c]{@{}l@{}}功能码; 例如，\\ 读取线圈/保持寄存器\end{tabular}   \\ \hline
    数据  & $n\times 8$ & \begin{tabular}[c]{@{}l@{}}数据+长度将根据\\ 消息类型填充\end{tabular}       \\ \hline
    CRC & 16          & 循环冗余校验                                                          \\ \hline
    结束  & 28         & \begin{tabular}[c]{@{}l@{}}帧之间至少有3.5个字符的\\ 空闲时间\end{tabular}    \\ \hline
    \end{tabular}
\end{table}

\subsection*{Modbus ASCII 帧格式（主要用于 7 位或 8 位异步串行线）}
消息中字符间发送的时间间隔最长不能超过1秒，否则接收的设备将认为传输错误。
\begin{table}[ht]
    \centering
    \caption{MODBUS ASCII帧格式}
    \label{tab:frame-formats-ascii}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    名称  & 长度(bytes)   & 功能                                                                         \\ \hline
    起始  & 1           & 以冒号开头：                                                                     \\ \hline
    地址  & 2           & 站地址                                                                        \\ \hline
    功能  & 2           & 功能码             \\ \hline
    数据  & $n\times 2$ & \begin{tabular}[c]{@{}l@{}}数据+长度将根据\\ 消息类型填充\end{tabular}                  \\ \hline
    LRC & 2           & 纵向冗余校验                                                                     \\ \hline
    结束  & 2           & \begin{tabular}[c]{@{}l@{}}回车 - 换行（CR / LF）对\\ （ASCII值为0D，0A）\end{tabular} \\ \hline
    \end{tabular}
\end{table}

地址，功能，数据和LRC都是表示 8 位值（0-255）的大写十六进制可读字符对。 
例如，122（7×16 + 10）将表示为 7A 。

LRC计算为 8 位值之和，取反（二进制补码）并编码为 8 位值。 
示例：如果地址，功能和数据编码为 247, 3, 19, 137, 0 和 10，则它们的和为 416。
转化为8位的二进制补码（-416）为 96（例如256×2 - 416），
它将以十六进制表示为 60。 因此，格式示例如下: F7031389000A60 <CR> <LF>。

\subsection*{Modbus TCP帧格式（主要用于以太网）}
\begin{table}[ht]
    \centering
    \caption{MODBUS TCP帧格式}
    \label{tab:frame-formats-tcp}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    名称    & 长度(bytes) & 功能                                                           \\ \hline
    传输标识符 & 2         & \begin{tabular}[c]{@{}l@{}}用于服务器和客户端\\ 的消息之间的同步\end{tabular} \\ \hline
    协议标识符 & 2         & \begin{tabular}[c]{@{}l@{}}0表示Modbus/TCP\\ 协议\end{tabular}   \\ \hline
    长度字段  & 2         & 此帧中的剩余字节数                                                    \\ \hline
    单元标识符 & 1         & \begin{tabular}[c]{@{}l@{}}从站地址（如果不使用\\ 则为255）\end{tabular}  \\ \hline
    功能码   & 1         & 功能码                                                          \\ \hline
    数据    & n         & 数据作为响应或命令                                                    \\ \hline
    \end{tabular}
\end{table}

单元标识符与 Modbus/TCP 设备一起使用，这些设备是几个 Modbus 设备的组合，
例如， 在 Modbus / TCP 到 Modbus RTU 网关上。 
在这种情况下，单元标识符告知网关后面的设备的从属地址。
本机 Modbus/TCP 功能设备通常忽略单元标识符。

\section{校验}
标准的Modbus串行网络采用两种错误检测方法。奇偶校验对每个字符都可用，
帧检测（LRC或CRC）应用于整个消息。它们都是在消息发送前由主设备产生的，
从设备在接收过程中检测每个字符和整个消息帧。\cite{ModbusTongXinXieYiXiangJieHonnnnl}

用户要给主设备配置一预先定义的超时时间间隔，这个时间间隔要足够长，
以使任何从设备都能作为正常反应。如果从设备测到一传输错误，消息将不会接收，
也不会向主设备作出回应。这样超时事件将触发主设备来处理错误。发往不存在的
从设备的地址也会产生超时。
\subsection*{奇偶校验}
用户可以配置控制器是奇或偶校验，或无校验。这将决定了每个字符中的奇偶校验位是如何设置的。

如果指定了奇或偶校验，“1”的位数将算到每个字符的位数中（ASCII模式7个数据位，RTU中8个
数据位）。例如RTU字符帧中包含以下8个数据位：1 1 0 0 0 1 0 1

整个“1”的数目是4个。如果便用了偶校验，帧的奇偶校验位将是0，便得整个“1”的个数仍是4个。
如果便用了奇校验，帧的奇偶校验位将是1，便得整个“1”的个数是5个。

如果没有指定奇偶校验位，传输时就没有校验位，也不进行校验检测。代替一附加的停止位填充
至要传输的字符帧中。
\subsection*{LRC校验}
使用ASCII模式，消息包括了一基于LRC方法的错误检测域。LRC域检测了消息域中除开始的冒号
及结束的回车换行号外的内容。

LRC域是一个包含一个8位二进制值的字节。LRC值由传输设备来计算并放到消息帧中，接收设备
在接收消息的过程中计算LRC，并将它和接收到消息中LRC域中的值比较，如果两值不等，说明有错误。

LRC方法是将消息中的8Bit的字节连续累加，丢弃了进位。
\subsection*{CRC校验}
CRC域是两个字节，包含一16位的二进制值。它由传输设备计算后加入到消息中。
接收设备重新计算收到消息的CRC，并与接收到的CRC域中的值比较，如果两值不同，则有误。

CRC是先调入一值是全“1”的16位寄存器，然后调用一过程将消息中连续的8位字节各当前
寄存器中的值进行处理。仅每个字符中的8Bit数据对CRC有效，起始位和停止位以及奇偶校验位均无效。

CRC产生过程中，每个8位字符都单独和寄存器内容相或（OR），结果向最低有效位方向移动，
最高有效位以0填充。LSB被提取出来检测，如果LSB为1，寄存器单独和预置的值或一下，
如果LSB为0，则不进行。整个过程要重复8次。在最后一位（第8位）完成后，下一个8位字节
又单独和寄存器的当前值相或。最终寄存器中的值，是消息中所有的字节都执行之后的CRC值。

CRC添加到消息中时，\emph{低字节先加入，然后高字节}。

\begin{itemize}
    \item 多项式：$x^{16} + x^{15} + x^2 + 1$（CRC-16-ANSI 也称为 CRC-16-IBM ，
    正常十六进制代数多项式为 8005，反转 A001）。
    初始值：65,535。
    \item 以十六进制表示的帧示例：01 04 02 FF FF B8 80
    （从01到FF的 CRC-16-ANSI 计算给出 80B8，它首先发送最低有效字节）。
\end{itemize}

\section{传输模式}
\subsection*{ASCII模式\cite{modbusorganizationMODBUSSerialLine2006}}
当控制器设为在Modbus网络上以ASCII（美国标准信息交换代码）模式通信，在消息中的
每个8Bit字节都作为两个ASCII字符发送。这种方式的主要优点是字符发送的时间间隔可
达到1秒而不产生错误。
\begin{itemize}
    \item 十六进制，ASCII字符0...9，A...F
    \item 消息中的每个ASCII字符都是一个十六进制字符组成每个字节的位
    \item 1个起始位
    \item 7个数据位，最小的有效位先发送
    \item 1个奇偶校验位，无校验则无
    \item 1个停止位（有校验时），2个Bit（无校验时）
    \item \gls{lrc}(纵向冗长检测)
\end{itemize}
\subsection*{RTU模式}
当控制器设为在Modbus网络上以RTU（远程终端单元）模式通信，在消息中的每个8Bit字节
包含两个4Bit的十六进制字符。这种方式的主要优点是：在同样的波特率下，可比ASCII方式
传送更多的数据。
\begin{itemize}
    \item 8位二进制，十六进制数0...9，A...F
    \item 消息中的每个8位域都是一个两个十六进制字符组成每个字节的位
    \item 1个起始位
    \item 8个数据位，最小的有效位先发送
    \item 1个奇偶校验位，无校验则无
    \item 1个停止位（有校验时），2个Bit（无校验时）
    \item \gls{crc}(循环冗长检测)
\end{itemize}

ASCII模式和RTU模式的详细对比见表~\ref{tab:ascii-rtu}。

\begin{table*}[hb] \centering % Using \begin{table*} makes the table take up the entire width of the page
    \caption{Modbus/ASCII 和 Modbus/RTU\cite{lammertbiesModbusProtocolSpecifications2019}}
    \label{tab:ascii-rtu}
    \begin{tabular}{|l|l|l|l|l|}
    \hline
                    & \multicolumn{2}{l|}{Modbus/ASCII}       & \multicolumn{2}{l|}{Modbus/RTU}            \\ \hline
    Characters      & \multicolumn{2}{l|}{ASCII 0…9 and A..F} & \multicolumn{2}{l|}{Binary 0…255}          \\ \hline
    Error check     & \multicolumn{2}{l|}{\gls{lrc}}          & \multicolumn{2}{l|}{\gls{crc}}             \\ \hline
    Frame start     & \multicolumn{2}{l|}{character ‘:‘}      & \multicolumn{2}{l|}{3.5 chars silence}     \\ \hline
    Frame end       & \multicolumn{2}{l|}{characters CR/LF}   & \multicolumn{2}{l|}{3.5 chars silence}     \\ \hline
    Gaps in message & \multicolumn{2}{l|}{1 sec}              & \multicolumn{2}{l|}{1.5 times char length} \\ \hline
    Start bit       & \multicolumn{2}{l|}{1}                  & \multicolumn{2}{l|}{1}                     \\ \hline
    Data bits       & \multicolumn{2}{l|}{7}                  & \multicolumn{2}{l|}{8}                     \\ \hline
    Parity          & even/odd             & none             & even/odd               & none              \\ \hline
    Stop bits       & 1                    & 2                & 1                      & 2                 \\ \hline
    \end{tabular}
\end{table*}

\section{地址}
\subsection*{设备地址}
每个终端设备的地址必须是唯一的，仅仅被寻址到的终端会响应包含了该地址的查询。
当终端发送回一个响应，响应中的从机地址数据便告诉了主机哪台终端正与之进行通信。
设备地址合法范围 0..247，1..247分配给独立节点，0为广播地址。
\subsection*{内部地址}
一些约定控制如何引用对 Modbus 实体（线圈，离散输入，输入寄存器，保持寄存器）的访问。
在实体编号和实体地址之间进行区分非常重要：
\begin{itemize}
    \item 实体编号在其描述表中组合实体类型和实体位置。
    \item 实体地址是起始地址，是 Modbus 帧数据部分中的16位值。因此它的范围从0到65535。
\end{itemize}

在传统标准中，这些实体的数字以数字开头，后跟 1-9,999 范围内的 4 位数字：
\begin{itemize}
    \item 线圈编号从 0 开始，跨度从 00001 到 09999，
    \item 离散输入数字从 1 开始，跨度从 10001 到 19999，
    \item 输入寄存器编号从 3 开始，跨度从 30001 到 39999，
    \item 保持寄存器号从 4 开始，跨度从 40001 到 49999。
\end{itemize}

这转换为数据帧中 0 到 9998 之间的地址。 例如，为了从数字 40001 开始读取保持寄存器，
数据帧中的相应地址将为 0，功能代码为 3。对于保持从编号 40100 开始的寄存器，地址将为 99 等等。

这将每个实体的地址数限制为9999 。事实上的引用将其扩展到最大值 65536。
\gls{pdu}寻址规则：在MODBUS PDU中每个数据有一个0到65535之间的地址。
它只是在前一个列表中添加一个数字：
\begin{itemize}
    \item 线圈编号从 000001 到 065536，
    \item 离散输入数字从 100001 到 165536，
    \item 输入寄存器编号范围从 300001 到 365536，
    \item 保持寄存器号码范围从 400001 到 465536。
\end{itemize}

使用扩展引用时，所有数字引用必须正好有 6 位数。这避免了线圈和其他实体之间的混淆。
例如，要知道保持寄存器＃40001 和线圈 ＃40001 之间的区别，如果线圈 ＃40001 是目标，
则它必须显示为 ＃040001\cite{controlsolutionsModbusTutorialControl}\cite{peterchipkinModbusFieldTechnicians2010}。

\section{功能码}
功能码告诉了被寻址到的终端执行何种功能。功能码合法范围为1..255。
表~\ref{tab:function-code}~列出了部分常用的功能码。
\begin{table}[ht]
    \centering
    \caption{MODBUS常用功能码}
    \label{tab:function-code}
    \begin{tabular}{|c|l|}
    \hline
    功能码    & 描述                       \\ \hline
    01       & Read coil status          \\ \hline
    02       & Read input status         \\ \hline
    03       & Read holding registers    \\ \hline
    04       & Read input registers      \\ \hline
    05       & Force single coil         \\ \hline
    06       & Preset single register    \\ \hline
    07       & Read exception status     \\ \hline
    15(0x0f) & Force multiple coils      \\ \hline
    16(0x10) & Preset multiple registers \\ \hline
    17(0x11) & Report slave ID           \\ \hline
    \end{tabular}
\end{table}

\subsection*{0x01:读取线圈状态}
每个线圈的值是二进制的（0 表示关闭，1 表示打开）。 首先请求的线圈被存储为作为答复的第一字节的最低有效位。
如果线圈的数量不是 8 的倍数，则最后一个字节的最高有效位将填充零。
例如，如果请求 11 个线圈，则需要两个字节的值。
假设这些连续线圈的状态为开，关，开，关，开，开，开，关，开，开，则响应将为十六进制的 02 E5 06 。

由于应答消息中返回的字节数仅为 8 位宽，协议开销为 5 字节，因此最多可以同时读取 2000（250 x 8）个线圈。
\begin{table}[htb]
    \centering
    \caption{功能码01查询帧}
    \label{tab:function01query}
    \begin{tabular}{|c|l|l|}
    \hline
    字节      & 值       & 描述       \\ \hline
    1       & 1...247 & 从设备地址    \\ \hline
    2       & 1       & 功能码      \\ \hline
    3       & 0...255 & 起始地址，高字节 \\ \hline
    4       & 0...255 & 起始地址，低字节 \\ \hline
    5       & 0...255 & 线圈数，高字节  \\ \hline
    6       & 0...255 & 线圈数，低字节  \\ \hline
    7(...8) & LRC/CRC & 校验值      \\ \hline
    \end{tabular}
\end{table}

\begin{table}[htb]
    \centering
    \caption{功能码01应答帧}
    \label{tab:function01answer}
    \begin{tabular}{|c|l|l|}
    \hline
    字节          & 值       & 描述     \\ \hline
    1           & 1...247 & 从设备地址  \\ \hline
    2           & 1       & 功能码    \\ \hline
    3           & 0...255 & 数据字节数N \\ \hline
    4...N+3     & 0...255 & 线圈状态值  \\ \hline
    N+4(...N+5) & LRC/CRC & 校验值    \\ \hline
    \end{tabular}
\end{table}

\subsection*{0x02:读取输入状态}
每个离散输入的值是二进制的（0 表示关闭，1 表示打开）。 首先请求的离散输入被存储为作为答复的第一字节的最低有效位。
如果离散输入的数量不是 8 的倍数，则最后一个字节的最高有效位将填充零。
例如，如果请求 11 个离散输入，则需要两个字节的值。
假设这些连续离散输入的状态为开，关，开，关，开，开，开，关，开，开，则响应将为十六进制的 02 E5 06 。

由于应答消息中返回的字节数仅为 8 位宽，协议开销为 5 字节，因此最多可以同时读取 2000（250 x 8）个离散输入。
\begin{table}[htb]
    \centering
    \caption{功能码02查询帧}
    \label{tab:function02query}
    \begin{tabular}{|c|l|l|}
    \hline
    字节      & 值       & 描述        \\ \hline
    1       & 1...247 & 从设备地址     \\ \hline
    2       & 2       & 功能码       \\ \hline
    3       & 0...255 & 起始地址，高字节  \\ \hline
    4       & 0...255 & 起始地址，低字节  \\ \hline
    5       & 0...255 & 输入点数量，高字节 \\ \hline
    6       & 0...255 & 输入点数量，低字节 \\ \hline
    7(...8) & LRC/CRC & 校验值       \\ \hline
    \end{tabular}
\end{table}

\begin{table}[htb]
    \centering
    \caption{功能码02应答帧}
    \label{tab:function02answer}
    \begin{tabular}{|c|l|l|}
    \hline
    字节          & 值       & 描述     \\ \hline
    1           & 1...247 & 从设备地址  \\ \hline
    2           & 1       & 功能码    \\ \hline
    3           & 0...255 & 数据字节数N \\ \hline
    4...N+3     & 0...255 & 输入点状态  \\ \hline
    N+4(...N+5) & LRC/CRC & 校验值    \\ \hline
    \end{tabular}
\end{table}
\FloatBarrier
\subsection*{0x03:读取保持寄存器}
本功能用于读取一块连续的保持寄存器。
在PDU中寄存器地址从0开始，因此输入寄存器1--16地址为0--15。
\begin{table}[htb]
    \centering
    \caption{功能码03查询帧}
    \label{tab:function03query}
    \begin{tabular}{|c|l|l|}
    \hline
    字节      & 值       & 描述        \\ \hline
    1       & 1...247 & 从设备地址     \\ \hline
    2       & 3       & 功能码       \\ \hline
    3       & 0...255 & 起始地址，高字节  \\ \hline
    4       & 0...255 & 起始地址，低字节  \\ \hline
    5       & 0...255 & 寄存器数量，高字节 \\ \hline
    6       & 0...255 & 寄存器数量，低字节 \\ \hline
    7(...8) & LRC/CRC & 校验值       \\ \hline
    \end{tabular}
\end{table}

\begin{table}[htb]
    \centering
    \caption{功能码03应答帧}
    \label{tab:function03response}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节          & 值       & 描述             \\ \hline
    1           & 1...247 & 从设备地址          \\ \hline
    2           & 3       & 功能码            \\ \hline
    3           & 0...255 & 字节数$2\times N$ \\ \hline
    4...N+3     & 0...255 & 寄存器值           \\ \hline
    N+4(...N+5) & LRC/CRC & 校验值            \\ \hline
    \end{tabular}
\end{table}
\FloatBarrier
\subsection*{0x04:读取输入寄存器}
在PDU中寄存器地址从0开始，因此输入寄存器1--16地址为0--15。
由于寄存器值的字节数为 8 位宽，因此一次只能读取 125 个寄存器。
\begin{table}[htb]
    \centering
    \caption{功能码04请求帧}
    \label{tab:function04request}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节      & 值       & 描述          \\ \hline
    1       & 1...247 & 从设备地址       \\ \hline
    2       & 4       & 功能码         \\ \hline
    3       & 0...255 & 起始地址，高字节    \\ \hline
    4       & 0...255 & 起始地址，低字节    \\ \hline
    5       & 0       & 输入寄存器数N，高字节 \\ \hline
    6       & 1...125 & 输入寄存器数N，低字节 \\ \hline
    7(...8) & LRC/CRC & 校验值         \\ \hline
    \end{tabular}
\end{table}

\begin{table}[htb]
    \centering
    \caption{功能码04应答帧}
    \label{tab:function04response}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节          & 值       & 描述             \\ \hline
    1           & 1...247 & 从设备地址          \\ \hline
    2           & 4       & 功能码            \\ \hline
    3           & 0...255 & 字节数$2\times N$ \\ \hline
    4...N+3     & 0...255 & 输入寄存器值         \\ \hline
    N+4(...N+5) & LRC/CRC & 校验值            \\ \hline
    \end{tabular}
\end{table}
\FloatBarrier
\subsection*{0x05:写单个线圈}
本功能用于写入单个输出。值0xFF00表示ON，值0x0000表示OFF。
任何其它值视为不合法，不会影响输出。
线圈地址从0开始，因此线圈1地址为0。
\begin{table}[htb]
    \centering
    \caption{功能码05请求帧}
    \label{tab:function05request}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节      & 值       & 描述       \\ \hline
    1       & 1...247 & 从设备地址    \\ \hline
    2       & 5       & 功能码      \\ \hline
    3       & 0...255 & 输出地址，高字节 \\ \hline
    4       & 0...255 & 输出地址，低字节 \\ \hline
    5       & 0或255   & 输出值，高字节  \\ \hline
    6       & 0       & 输出值，低字节  \\ \hline
    7(...8) & LRC/CRC & 校验值      \\ \hline
    \end{tabular}
\end{table}

\begin{table}[hbt]
    \centering
    \caption{功能码05应答帧}
    \label{tab:function05response}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节      & 值       & 描述       \\ \hline
    1       & 1...247 & 从设备地址    \\ \hline
    2       & 5       & 功能码      \\ \hline
    3       & 0...255 & 输出地址，高字节 \\ \hline
    4       & 0...255 & 输出地址，低字节 \\ \hline
    5       & 0或255   & 输出值，高字节  \\ \hline
    6       & 0       & 输出值，低字节  \\ \hline
    7(...8) & LRC/CRC & 校验值      \\ \hline
    \end{tabular}
\end{table}
\FloatBarrier
\subsection*{0x06:写单个寄存器}
本功能用于写入单个保持寄存器。寄存器地址从0开始，因此寄存器1地址为0。
\begin{table}[htb]
    \centering
    \caption{功能码06请求帧}
    \label{tab:function06request}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节      & 值       & 描述        \\ \hline
    1       & 1...247 & 从设备地址     \\ \hline
    2       & 6       & 功能码       \\ \hline
    3       & 0...255 & 寄存器地址，高字节 \\ \hline
    4       & 0...255 & 寄存器地址，低字节 \\ \hline
    5       & 0...255 & 寄存器值，高字节  \\ \hline
    6       & 0...255 & 寄存器值，低字节  \\ \hline
    7(...8) & LRC/CRC & 校验值       \\ \hline
    \end{tabular}
\end{table}

\begin{table}[htb]
    \centering
    \caption{功能码06应答帧}
    \label{tab:function06response}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节      & 值       & 描述        \\ \hline
    1       & 1...247 & 从设备地址     \\ \hline
    2       & 6       & 功能码       \\ \hline
    3       & 0...255 & 寄存器地址，高字节 \\ \hline
    4       & 0...255 & 寄存器地址，低字节 \\ \hline
    5       & 0...255 & 寄存器值，高字节  \\ \hline
    6       & 0...255 & 寄存器值，低字节  \\ \hline
    7(...8) & LRC/CRC & 校验值       \\ \hline
    \end{tabular}
\end{table}
\FloatBarrier
\subsection*{0x0f:写多个线圈}
本功能用于强制连续的一系列线圈处于ON或OFF状态。
线圈地址从0开始，因此线圈1地址为0。
\begin{table}[htp]
    \centering
    \caption{功能码15请求帧}
    \label{tab:function15request}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节          & 值       & 描述       \\ \hline
    1           & 1...247 & 从设备地址    \\ \hline
    2           & 0x0f    & 功能码      \\ \hline
    3           & 0...255 & 起始地址，高字节 \\ \hline
    4           & 0...255 & 起始地址，低字节 \\ \hline
    5           &         & 输出数量，高字节 \\ \cline{1-1} \cline{3-3} 
    6           & \multirow{-2}{*}{1...0x7B0} & 输出数量，低字节 \\ \hline
    7           & 0...255 & 字节数N     \\ \hline
    8...N+7     & 0...255 & 输出值      \\ \hline
    N+8(...N+9) & LRC/CRC & 校验值      \\ \hline
    \end{tabular}
\end{table}

\begin{table}[htb]
    \centering
    \caption{功能码15应答帧}
    \label{tab:function15response}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节      & 值                           & 描述       \\ \hline
    1       & 1...247                     & 从设备地址    \\ \hline
    2       & 0x0f                        & 功能码      \\ \hline
    3       & 0...255                     & 起始地址，高字节 \\ \hline
    4       & 0...255                     & 起始地址，低字节 \\ \hline
    5       &                             & 输出数量，高字节 \\ \cline{1-1} \cline{3-3} 
    6       & \multirow{-2}{*}{1...0x7B0} & 输出数量，低字节 \\ \hline
    7(...8) & LRC/CRC                     & 校验值      \\ \hline
    \end{tabular}
\end{table}
\FloatBarrier
\subsection*{0x10:预置/写入多个保持寄存器}
本功能用于写入一块连续的寄存器（1至123个寄存器）。
由于寄存器值为 2 字节宽，并且只能发送 127 个字节的值，
因此一次只能预置/写入 63 个保持寄存器。

\begin{table}[htb]
    \centering
    \caption{功能码16请求帧}
    \label{tab:function16request}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节           & 值           & 描述        \\ \hline
    1             & 1...247     & 从设备地址     \\ \hline
    2             & 0x10        & 功能码       \\ \hline
    3             & 0...255     & 起始地址，高字节  \\ \hline
    4             & 0...255     & 起始地址，低字节  \\ \hline
    5             & 0           & 寄存器数N，高字节 \\ \hline
    6             & 1...123     & 寄存器数N，低字节 \\ \hline
    7             & $2\times N$ & 字节数       \\ \hline
    8...2N+7    & 0...255     & 寄存器值      \\ \hline
    2N+8(...2N+9) & LRC/CRC & 校验值      \\ \hline
    \end{tabular}
\end{table}

\begin{table}[htb]
    \centering
    \caption{功能码16应答帧}
    \label{tab:function16response}
    \begin{tabular}{|c|c|l|}
    \hline
    \rowcolor[HTML]{FFFFC7} 
    字节      & 值                          & 描述        \\ \hline
    1       & 1...247                    & 从设备地址     \\ \hline
    2       & 0x10                       & 功能码       \\ \hline
    3       & 0...255                    & 起始地址，高字节  \\ \hline
    4       & 0...255                    & 起始地址，低字节  \\ \hline
    5       &                            & 寄存器数量，高字节 \\ \cline{1-1} \cline{3-3} 
    6       & \multirow{-2}{*}{1...0x7B} & 寄存器数量，低字节 \\ \hline
    7(...8) & LRC/CRC                    & 校验值       \\ \hline
    \end{tabular}
\end{table}
\FloatBarrier
\section{异常响应}
对于正常响应，从站重复功能代码。 如果从站想报告错误，
它将回复所请求的功能代码加上 128（十六进制 0x80）
（3 变为 131 = 十六进制 0x83），并且只包含一个字节的数据，
称为\emph{异常代码}。
\subsection*{主要异常代码}
\begin{description}
    \item [0x01] 非法功能 -- 从设备无法识别或允许在查询中接收的功能代码
    \item [0x02] 非法数据地址 -- 从设备中不允许或不存在部分或全部所需实体的数据地址
    \item [0x03] 非法数据值 -- 从设备不接受该数据
    \item [0x04] 从设备故障 -- 从设备尝试执行请求的操作时发生不可恢复的错误
    \item [0x05] 确认 -- 从设备已接受请求并正在处理它，但需要很长的时间。
                 返回此响应以防止在主设备中发生超时错误。 主设备可以接下来发出一个 
                 Poll Program Complete 消息来确定处理是否完成
    \item [0x06] 从设备忙 -- 从设备参与处理长时间命令。主设备应该稍后再试
    \item [0x07] 否认 -- 从设备无法执行编程功能。 主设备应从从设备请求诊断或错误信息
    \item [0x08] 内存奇偶校验错误 -- 从设备检测到内存中的奇偶校验错误。
                 主设备可以重试请求，但可能需要在从设备上提供服务
    \item [0x0a] 网关路径不可用 -- 专门用于 Modbus 网关。表示配置错误的网关
    \item [0x0b] 网关目标设备无法响应 -- 专门用于 Modbus 网关。从站无法响应时发送
\end{description}

\section{限制}
\begin{itemize}
    \item 由于Modbus是在 20 世纪 70 年代后期设计的，用于与可编程逻辑控制器通信，
          因此数据类型的数量仅限于 PLC 当时所理解的数据类型。不支持大型二进制对象。
    \item 节点没有标准方法来查找数据对象的描述，例如，确定寄存器值是否表示 30 到 
          175 度之间的温度。
    \item 由于 Modbus 是主/从协议，因此现场设备无法“异常报告”（以太网 TCP/IP 除外，
          称为 open-mbus） - 主节点必须定期轮询每个现场设备并查找更改在数据中。
          这在带宽可能昂贵的应用中消耗带宽和网络时间，例如通过低比特率无线电链路。
    \item Modbus 仅限于在一条数据链路上寻址 254 个设备，这限制了可能连接到主站的
          现场设备的数量（再次，以太网 TCP/IP 是一个例外）。
    \item Modbus 传输必须是连续的，这将远程通信设备的类型限制为可以缓冲数据以避免
          传输中的间隙的设备。
    \item Modbus协议本身不能防止未经授权的命令或数据拦截。
\end{itemize}

%----------------------------------------------------------------------------------------
%	REFERENCE LIST
%----------------------------------------------------------------------------------------
\phantomsection
\bibliographystyle{unsrt}
\bibliography{reference}

%------------------------------------------------------------------------------------
%Print the glossary
\printglossary[title=专业术语和缩略语, toctitle=专业术语和缩略语]

\end{document}
